package de.luh.slimpp.generator

import de.luh.slimpp.slimPP.ClassDefinition
import de.luh.slimpp.slimPP.Method
import de.luh.slimpp.slimPP.Model
import de.luh.slimpp.slimPP.NamespaceDefinition
import de.luh.slimpp.slimPP.QualifiedName
import de.luh.slimpp.slimPP.TopLevelDefinition
import java.util.ArrayList
import java.util.HashMap
import java.util.List
import java.util.Map
import de.luh.slimpp.slimPP.TypeName
import static extension de.luh.slimpp.generator.TypeNameExtensions.*

class ModelPreprocessor {
	val enhancedClasses = new HashMap<QualifiedName, EnhancedClass>
	var ExternalHeaderFinder externalTypes = null

	def process(Model model) {
		if (externalTypes == null) {
			externalTypes = new ExternalHeaderFinder('type_to_header.txt')
		}

		model.definitions.assignFullNames(emptyList)

		val allClasses = new ArrayList<ClassDefinition>
		allClasses.addAll(model.eAllContents.toIterable.filter(typeof(ClassDefinition)))
		val nameToClass = new HashMap<QualifiedName, ClassDefinition>

		allClasses.forEach[nameToClass.put(it.qualifiedName, it)]

		val scopeFinder = new ScopeFinder<ClassDefinition>
		allClasses.forEach[scopeFinder.put(it.qualifiedName, it)]

		allClasses.forEach[resolve(nameToClass, scopeFinder)]
	}

	def getEnhancedClass(ClassDefinition cls) {
		enhancedClasses.get(cls.qualifiedName)
	}

	private def EnhancedClass resolve(ClassDefinition original, Map<QualifiedName, ClassDefinition> allClasses,
		ScopeFinder<ClassDefinition> classFinder) {

		println('resolving ' + original.qualifiedName.name)

		var cls = enhancedClasses.get(original.qualifiedName)

		// if the class has not been handled yet we need to do that
		if (cls == null) {
			cls = new EnhancedClass(original)
			enhancedClasses.put(original.qualifiedName, cls)
			println(enhancedClasses.size + ' enhanced classes known')

			// assign the base classes
			for (base : original.bases) {
				val baseClass = classFinder.find(base.qualifiedName, original.qualifiedName)
				if (baseClass != null) {

					// establish the connections between base and sub class
					val enhancedBaseClass = resolve(baseClass, allClasses, classFinder)
					cls.superclasses.add(enhancedBaseClass)
					enhancedBaseClass.subclasses.add(cls)
				} else { // the base class could not be resolved => TODO
					throw new Exception(
						'base class ' + base.qualifiedName.name + ' not part of the model, TODO what now?')
				}
			}

			// assign all methods defined in this class
			for (method : original.defs.filter(typeof(Method))) {
				val enhancedMethod = new EnhancedMethod(cls, method, false)
				cls.methods.add(enhancedMethod)
			}

			// now find all methods from super classes that are overridden in this class
			if (original.type.isClass || original.implementsAll) {

				println('class')

				// if this is a regular class we want it to implement all pure virtual methods
				// that were declared in its super classes
				for (base : cls.superclasses) { // TODO was allSuperClasses
					for (method : base.unimplementedPureVirtualMethods) {
						if (method == null)
							println(' method == null')
						else if (method.original == null)
							println(' original == null')
						else if(method.original.name == null) println(' name == null')
						println(' implementing ' + method.original.name)
						val enhancedMethod = new EnhancedMethod(cls, method.original, true)
						cls.methods.add(enhancedMethod)
					}
				}

			} else if (original.type.interface || original.implementsNone) {

				println('interface')

				// an interface can only have interfaces as super classes so all we need to do
				// is copy all their methods to this class as pure virtual
				for (base : cls.superclasses) {
					for (method : base.unimplementedPureVirtualMethods) {
						val enhancedMethod = new EnhancedMethod(cls, method.original, false)
						cls.unimplementedPureVirtualMethods.add(enhancedMethod)
					}
				}

				// since this is an interface all methods are pure virtual
				cls.unimplementedPureVirtualMethods.addAll(cls.methods)

				println(' has ' + cls.unimplementedPureVirtualMethods.size + ' unimpl. methods')

			} else if (original.type.abstract) {

				// at this point there is an 'implements' statement specified and we have to
				// find the overridden methods in the base classes
				// first add all unimplemented methods, later we remove the implemented ones
				for (base : cls.superclasses) {
					for (method : base.unimplementedPureVirtualMethods) {
						val enhancedMethod = new EnhancedMethod(cls, method.original, true)
						cls.unimplementedPureVirtualMethods.add(enhancedMethod)
					}
				}

				// now sort out all the implemented methods
				for (impl : original.implements.implementations) {
					if (impl.methodName != null) {

						// find method by name
						val method = cls.unimplementedPureVirtualMethods.findFirst[it.original.name == impl.methodName]
						if (method != null) {
							cls.unimplementedPureVirtualMethods.remove(method)
							cls.methods.add(method)
						}
					} else if (impl.signature != null) {

						// TODO
						println('TODO find method by signature')
					}
				}

			}

			if (original.type.abstract && original.implements != null && !original.implementsNone) {

				// add all methods that were declared virtual in Slim++ thus should be pure virtual in the
				// generated code
				cls.unimplementedPureVirtualMethods.addAll(cls.methods.filter[it.original.virtual])
			}

			// add all typedef names
			for (typedef : original.typeDefs) {
				cls.typedefs.add(typedef.name)
			}

			// now resolve the includes
			println("resolving includes for " + original.qualifiedName.name)

			// include the base classes don't forget the base classes, they have to be included for sure
			for (base : original.bases) {
				if (classFinder.find(base.qualifiedName, original.qualifiedName) != null) {
					val names = new ArrayList<String>
					names.addAll(base.qualifiedName.namespaces)
					names.add(base.qualifiedName.name)
					cls.includes.add('"' + names.join('/') + '.h"')
				} else {
					throw new Exception('base class ' + base.qualifiedName.name + ' not found, TODO')
				}
			}

			// look at all types used in the class to know what to include or forward declare
			for (t : original.eAllContents.toIterable.filter(typeof(TypeName))) {

				// first try to find the class as part of the model
				val names = t.getQualifiedInternalName
				println()
				val internal = classFinder.find(names, original.qualifiedName)
				if (internal != null) {
					cls.includes.add('"' + names.join('/') + '.h"')
				} else {

					// if the type is not part of the model, we try to find it in an external header
					// also the type may be made up of several types (e.g. as template parameters) so
					// we resolve them as well
					val type = allTypesAsStrings(t)
					val header = externalTypes.findHeader(type)
					if (header != null) {
						cls.includes.add(header)
					}
				}
			}

			// in case the class referenced itself in some way we do not want its own header file to be
			// included so we just remove it from the includes (if it is not in there, nothing will happen)
			val myHeader = '"' + original.qualifiedName.namespaces.join('', '/', '/', [it]) +
				original.qualifiedName.name + '.h"'
				cls.includes.remove(myHeader)
		}

		println(original.qualifiedName.name + ' resolved')
		return cls
	}

	def allTypesAsStrings(TypeName typeName) {
		var type = typeName.type
		val x = new StringBuilder(type.name)
		while (type.templateArgument == null && type.hasNext) {
			type = type.nextType
			x.append('::')
			x.append(type.name)
		}
		return x.toString
	}

	def implementsAll(ClassDefinition cls) {
		cls.type.abstract && cls.implements != null && cls.implements.all
	}

	def implementsNone(ClassDefinition cls) {
		cls.type.abstract && (cls.implements == null || (cls.implements != null && cls.implements.none))
	}

	/**
* Assigns all classes within a namespace its name, e.g. if class A is in namespace b
* then the qualified name of A will be b.A after this method was called.
* Applying this method makes handling NamespaceDefinitions unnecessary as they are
* implicitly contained in the class' qualified names then.
*/
	private def void assignFullNames(List<TopLevelDefinition> defs, List<String> namespaces) {
		defs.filter(typeof(ClassDefinition)).forEach[qualifiedName.namespaces.addAll(0, namespaces)]
		for (ns : defs.filter(typeof(NamespaceDefinition))) {
			val subNamespaces = newArrayList(ns.name.name)
			subNamespaces.addAll(0, ns.name.namespaces)
			subNamespaces.addAll(0, namespaces)
			ns.definitions.assignFullNames(subNamespaces)
		}
	}
}
